#!/usr/bin/env python3
#
# @file HiUsbCamera.py
# @brief
# @author QRS
# @version 1.0
# @date 2024-04-02 22:12

import sys
import cv2
import numpy as np

sys.path.append(r'C:\Program Files (x86)\MVS\Development\Samples\Python\MvImport')

from ctypes import (
    cast,
    memset,
    byref,
    sizeof,
    c_ubyte,
    POINTER,
)

from Utils import (
    ErrorCode,
    WSSError,
    Logger,
)

from MvCameraControl_class import (
    MV_CC_DEVICE_INFO_LIST,
    MV_USB_DEVICE,
    MV_CC_DEVICE_INFO,
    MVCC_INTVALUE,
    MV_TRIGGER_MODE_OFF,
    MV_FRAME_OUT_INFO_EX,
    MV_ACCESS_Exclusive,
    MvCamera,
)

class HiCamera(object):

    def __init__(self, serial_number):
        self._serial_number = serial_number
        self._camera = None
        self._reader = None
        self._frame_width = -1
        self._frame_height = -1
        self._frame_buffer = None

    def __del__(self):
        self.close()

    @property
    def did(self):
        return self._serial_number

    @staticmethod
    def list():
        deviceList = MV_CC_DEVICE_INFO_LIST()
        ret = MvCamera.MV_CC_EnumDevices(MV_USB_DEVICE, deviceList)
        if ret != 0:
            raise WSSError(ErrorCode.MV_CC_ENUMDEVICES, 'ret[0x%x]' % ret)

        Logger.info('Find %d devices!' % deviceList.nDeviceNum)

        devices = []
        for i in range(0, deviceList.nDeviceNum):
            mvcc_dev_info = cast(deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
            if mvcc_dev_info.nTLayerType == MV_USB_DEVICE:
                Logger.info('\nu3v device: [%d]' % i)
                strModeName = ''
                for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chModelName:
                    if per == 0:
                        break
                    strModeName = strModeName + chr(per)
                Logger.info('device model name: %s' % strModeName)

                strSerialNumber = ''
                for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chSerialNumber:
                    if per == 0:
                        break
                    strSerialNumber = strSerialNumber + chr(per)
                Logger.info('user serial number: %s' % strSerialNumber)
                devices.append(mvcc_dev_info)
        return devices

    @staticmethod
    def get_serial_number(dinfo):
        strSerialNumber = ''
        for per in dinfo.SpecialInfo.stUsb3VInfo.chSerialNumber:
            if per == 0:
                break
            strSerialNumber = strSerialNumber + chr(per)
        return strSerialNumber

    def open(self, dinfo):
        cam = MvCamera()
        ret = cam.MV_CC_CreateHandle(dinfo)
        if ret != 0:
            raise WSSError(ErrorCode.MV_CC_CREATEHANDLE, 'ret[0x%x]' % ret)

        ret = cam.MV_CC_OpenDevice(MV_ACCESS_Exclusive, 0)
        if ret != 0:
            raise WSSError(ErrorCode.MV_CC_OPENDEVICE, 'ret[0x%x]' % ret)

        ret = cam.MV_CC_SetEnumValue('TriggerMode', MV_TRIGGER_MODE_OFF)
        if ret != 0:
            raise WSSError(ErrorCode.MV_SET_TRIGGERMODE, 'ret[0x%x]' % ret)

        self._camera = cam
        self._reader = self._read(cam)

        return self

    def _read(self, cam, timeout=2000):
        stParam = MVCC_INTVALUE()
        memset(byref(stParam), 0, sizeof(MVCC_INTVALUE))
        ret = cam.MV_CC_GetIntValue('PayloadSize', stParam)
        if ret != 0:
            raise WSSError(ErrorCode.MV_GET_PAYLOADSIZE, 'ret[0x%x]' % ret)

        nPayloadSize = stParam.nCurValue
        Logger.info(f'nPayloadSize: {nPayloadSize}')

        pData = (c_ubyte * nPayloadSize)()

        ret = cam.MV_CC_StartGrabbing()
        if ret != 0:
            raise WSSError(ErrorCode.MV_CC_STARTGRABBING, 'ret[0x%x]' % ret)

        stFrameInfo = MV_FRAME_OUT_INFO_EX()
        memset(byref(stFrameInfo), 0, sizeof(stFrameInfo))

        self._frame_buffer = pData
        while True:
            ret = cam.MV_CC_GetOneFrameTimeout(pData, nPayloadSize, stFrameInfo, timeout)
            if ret != 0:
                continue
            if self._frame_width < 0:
                self._frame_width = stFrameInfo.nWidth
                self._frame_height = stFrameInfo.nHeight
            frame = np.asarray(pData).reshape((stFrameInfo.nHeight, stFrameInfo.nWidth))
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            yield frame

    def read(self):
        if self._reader is None:
            raise WSSError(ErrorCode.WSS_CAM_NOTDETECT, 'camera is not opened')
        return next(self._reader)

    def close(self):
        if self._camera is None:
            return
        cam, buf = self._camera, self._frame_buffer
        self._camera, self._frame_buffer = None, None

        ret = cam.MV_CC_StopGrabbing()
        if ret != 0:
            del buf
            raise WSSError(ErrorCode.MV_CC_STOPGRABBING, "ret[0x%x]" % ret)

        ret = cam.MV_CC_CloseDevice()
        if ret != 0:
            del buf
            raise WSSError(ErrorCode.MV_CC_CloseDevice, "ret[0x%x]" % ret)

        ret = cam.MV_CC_DestroyHandle()
        if ret != 0:
            del buf
            raise WSSError(ErrorCode.MV_CC_DESTROYHANDLE, "ret[0x%x]" % ret)

        del buf
